/**
 * 
 */
package jvs.peer.vfs;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayList;

import jvs.peer.io.ChannelManager;
import jvs.peer.io.PeerStatus.Status;
import jvs.peer.xmpp.XmppFileUtil;
import jvs.vfs.IFile;
import jvs.vfs.IFileBaseImpl;
import x.java.io.File.Info;

/**
 * @author qiangli
 * 
 */
public class PeerFileImpl extends IFileBaseImpl {
	private static final String SCHEME = "peer";

	private Info info;

	private ArrayList<Info> list;

	private String path;

	private String peer;

	private boolean loaded;

	/**
	 * @param uri
	 */
	public PeerFileImpl(URI uri) {
		super(uri);
		// peer:/peer/path
		if (uri.getScheme() == null
				|| !uri.getScheme().equalsIgnoreCase(SCHEME)) {
			throw new IllegalArgumentException("incorrect scheme: "
					+ uri.getScheme());
		}
		//
		String s = uri.getPath();
		// /jid/path
		int i2 = s.indexOf("/", 1);
		// top level - peer with path == null
		if (i2 == -1) {
			peer = s.substring(1);
			path = null;
			return;
		}
		// remote file from peer
		peer = s.substring(1, i2);
		path = s.substring(i2);
		if (path.equals("/")) {
			path = null;
		}
	}

	protected PeerFileImpl(String peer, String path) {
		this.peer = peer;
		this.path = path;
	}

	private PeerFileImpl(String peer, String path, Info info) {
		this.peer = peer;
		this.path = path;
		this.info = info;
	}

	@Override
	public boolean canRead() {
		if (!exists()) {
			return false;
		}
		if (path == null) {
			return true;
		}
		return (info != null) && info.canRead;
	}

	@Override
	public boolean canWrite() {
		if (!exists()) {
			return false;
		}
		if (path == null) {
			return false;
		}
		return (info != null) && info.canWrite;
	}

	@Override
	public boolean create() {
		return false; // TODO readonly for now
	}

	@Override
	public boolean delete() {
		return false;
	}

	@Override
	public boolean exists() {
		// /peer/path

		// full/valid jid required. e.g. index.htmf is not a valid peer id
		// at least one @ and one .
		if (peer == null || peer.indexOf('@') == -1 || peer.indexOf('.') == -1) {
			return false;
		}
		if (getAccessControl().checkPrincipal(peer) == false) {
			return false;
		}
		// remote peer root
		if (path == null) {
			return true;
		}
		//
		loadInfo();
		if (info == null) {
			return false;
		}
		return true;
	}

	@Override
	public InputStream getInputStream() {
		try {
			return XmppFileUtil.pullAsStream(peer, path == null ? "/" : path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	@Override
	public long getLastModified() {
		if (path == null) {
			return fs.getUptime();
		}
		loadInfo();
		if (info == null) {
			return 0;
		}
		return info.lastModified;
	}

	@Override
	public long getLength() {
		if (path == null) {
			return 0;
		}
		loadInfo();
		if (info == null) {
			return 0;
		}
		return info.length;
	}

	@Override
	public OutputStream getOutputStream() {
		return null; // TODO
	}

	@Override
	public boolean isDirectory() {
		// top
		if (path == null) {
			return true;
		}
		//
		loadInfo();
		if (info != null) {
			return info.isDirectory;
		}
		//
		return false;
	}

	@Override
	public boolean isFile() {
		if (path == null) {
			return false;
		}
		//
		loadInfo();
		if (info != null) {
			return info.isFile;
		}
		//
		return true;
	}

	@Override
	public boolean isLocal() {
		return (path == null); // remote if path has value
	}

	@Override
	public boolean isNative() {
		return false;
	}

	@Override
	public boolean isEmpty() {
		if (path == null) {
			return false;
		}
		return super.isEmpty();
	}

	@Override
	public String[] list() {
		loadInfo();
		if (info != null && !info.isFile) {
			loadList();
			if (list == null) {
				return new String[0];
			}
			String[] l = new String[list.size()];
			int i = 0;
			for (Info fi : list) {
				l[i++] = fi.name;
			}
			return l;
		}
		//
		if (isDirectory()) {
			return new String[0];
		}
		return null;
	}

	public IFile[] listFiles() {
		loadInfo();
		if (info != null && !info.isFile) {
			try {
				loadList();
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (list == null) {
				return new IFile[0];
			}
			//
			int n = list.size();
			IFile[] fs = new IFile[n];
			String p = (path == null ? "/" : path);
			p = (p.endsWith("/") ? p : p + "/");
			int i = 0;
			for (Info fi : list) {
				try {
					String s = p + URLEncoder.encode(fi.name, "UTF-8");
					fs[i++] = new PeerFileImpl(peer, s, fi);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return fs;
		}
		//
		if (isDirectory()) {
			return new IFile[0];
		}
		return null;
	}

	@Override
	public IFile[] listFiles(int off, int max) {
		loadInfo();
		if (info != null && !info.isFile) {
			try {
				loadList();
			} catch (Exception e) {
				e.printStackTrace();
			}
			if (list == null) {
				return new IFile[0];
			}
			//
			int len = list.size();
			if (off >= len) {
				return new IFile[0];
			}
			// assume sorted
			len = Math.min(len, off + max);
			IFile[] rl = new IFile[len - off];
			String p = (path == null ? "/" : path);
			p = (p.endsWith("/") ? p : p + "/");
			for (int i = off; i < len; i++) {
				// try {
				Info fi = list.get(i);
				try {
					String s = p + URLEncoder.encode(fi.name, "UTF-8");
					rl[i - off] = new PeerFileImpl(peer, s, fi);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
			return rl;
		}
		//
		if (isDirectory()) {
			return new IFile[0];
		}
		return null;
	}

	synchronized private void loadInfo() {
		if (loaded) {
			return;
		}
		loaded = true;
		try {
			ChannelManager cm = ChannelManager.getInstance();
			if (cm.isLocal(peer)) {
				return; //ignore
			}
			Status st = cm.checkStatus(peer);
			if (st.equals(Status.AVAIL) == false) {
				throw new RuntimeException("Peer not available");
			}
			//
			if (XmppFileUtil.supportsShell(peer) == false) {
				throw new RuntimeException("Shell not supported");
			}
			//
			info = XmppFileUtil.info(peer, path == null ? "/" : path);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
			info = null;
		} catch (RuntimeException e) {
			info = null;
			throw e;
		} catch (Exception e) {
			info = null;
			e.printStackTrace();
			throw new RuntimeException("Can't access remote peer file. " + e);
		}
	}

	synchronized private void loadList() {
		if (list == null) {
			try {
				list = new ArrayList<Info>();
				XmppFileUtil.list(peer, path == null ? "/" : path, 0,
						Integer.MAX_VALUE, list);
			} catch (Exception e) {
				list = null;
				e.printStackTrace();
				throw new RuntimeException("Failed to list remote file. " + e);
			}
		}
	}

	@Override
	public boolean mkdir() {
		return false;
	}

	@Override
	public boolean move(URI uri) {
		return false;
	}

	@Override
	public void refresh() {
		info = null;
		list = null;
	}

	@Override
	public String getName() {
		if (path == null) {
			return peer;
		}
		return name(path);
	}

	@Override
	protected String getParent() {
		if (path == null) {
			return null;
		}
		return parent(path);
	}

	@Override
	public URI toRealURI() {
		return URI.create(SCHEME + ":/" + peer + (path == null ? "" : path));
	}

	@Override
	public String toString() {
		return SCHEME + ":/" + peer + (path == null ? "" : path);
	}
}
